/*
 -- IOTA Crypto Core
 --
 -- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
 -- discord: pmaxuw#8292
 -- https://gitlab.com/iccfpga-rv
 --
 -- Permission is hereby granted, free of charge, to any person obtaining
 -- a copy of this software and associated documentation files (the
 -- "Software"), to deal in the Software without restriction, including
 -- without limitation the rights to use, copy, modify, merge, publish,
 -- distribute, sublicense, and/or sell copies of the Software, and to
 -- permit persons to whom the Software is furnished to do so, subject to
 -- the following conditions:
 --
 -- The above copyright notice and this permission notice shall be
 -- included in all copies or substantial portions of the Software.
 --
 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 -- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 -- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
 */

#include <stdint.h>
#include <string.h>

#include "main.h"
#include "riscv.h"
#include "riscv-arch/arch-defines.h"

#include "debugprintf.h"

#include "utils.h"

#include "secure/sbi.h"
#include "secure/secure.h"
#include "gpio/secure_gpio.h"

#include "fpga/config.h"
#include "fpga/conversion_fpga.h"
#include "fpga/pow.h"
#include "fpga/sha3.h"
#include "keccak/sha3.h"

#include "iota/addresses.h"
#include "iota/signing.h"
#include "iota/bundle.h"
#include "iota/transfers.h"

#include "fpga/test.h"

#include "qspi/qspi.h"

#include "api/API.h"

#include "ssd1331/ui.h"

extern const uint32_t SPIFlash::FLASH_PAGE_LENGTH_BASE64;

// requests and responses in normal memory
// defined as static objects for security-reasons
SBIResponse 				statusResponse;

DoPoWRequest 				doPoWRequest;
DoPoWResponse 				doPoWResponse;

CurlHashRequest  			curlHashRequest;
CurlHashResponse 			curlHashResponse;

SignBundleHashRequest 		signBundleHashRequest;
SignBundleHashResponse 		signBundleHashResponse;

//
TestAccelerationResponse 	testAccelerationResponse;

ReadFlashRequest 			readFlashRequest;
ReadFlashResponse 			readFlashResponse;

SetRandomSeedRequest 		setRandomSeedRequest;
//

//
GetRandomBytesResponse		getRandomBytesResponse;

GetAddressRequest 			getAddressRequest;
GetAddressResponse			getAddressResponse;

PrepareTransfersResponse	prepareTransfersResponse;
PrepareTransfersRequest		prepareTransfersRequest;

InitSecureElementRequest 	initSecureElementRequest;
//

WriteFlashRequest 			writeFlashRequest;
//

RebootRequest 				rebootRequest;
//

SetActiveSeedRequest 		setActiveSeedRequest;
//

//
MCycleResponse				mCycleResponse;

// enable / disable fpga for type conversions
// object in MMDATA because it could contain sensitive date
#if 0
#ifdef CONVERSION_FPGA
ConversionFPGA MMDATA conv;
#else
Conversion MMDATA conv;
#endif
#endif
// object in MMDATA because it could contain sensitive date
#ifdef KECCAK384_FPGA
Keccak384FPGA MMDATA keccak384;
#else
Keccak384 MMDATA keccak384;
#endif

BundleCTX MMDATA bundle;

//Conversion* pConversion = &conv;
Keccak384* pKeccak384 = &keccak384;

Pow MMDATA powFPGA;


extern Transaction txs[MAX_NUM_TRANSACTIONS];
extern buffer_t buffer;

extern API::apiFlagsStruct apiFlags;

// eclipse complains it doesn't now strnlen ...
// ... so define it here as prototype
size_t strnlen(const char *s, size_t maxlen);

// from lowlevel
uint64_t getMCycle();

const char* SBIResponse::errorMsg(SBIError code) {
	switch(code) {
	case NONE: return "ok";
	case SE_NOT_INITIALIZED: return "secure element not initialized";
	case INVALID_SLOT: return "invalid slot";
	case INVALID_KEY_INDEX: return "invalid key index";
	case INVALID_SECLEVEL: return "invalid secure level";
	case ERROR_LOADING_KEY: return "error loading key";
	case INVALID_KEY: return "invalid key";
	case ERROR_START_SE: return "error starting secure element";
	case ERROR_SET_RANDOM_KEY: return "error setting random key";
	case INVALID_BUNDLE_HASH: return "invalid bundlehash";
	case INVALID_AUTH: return "auth invalid";
	case INSECURE_BUNDLE_HASH: return "insecure bundlehash";
	case INVALID_TRYTES: return "invalid trytes";
	case INVALID_BRANCH: return "invalid branch";
	case INVALID_TRUNK: return "invalid trunk";
	case INVALID_MWM: return "invalid mwm";
	case TOO_MANY_TX: return "too many transactions";
	case INVALID_ADDRESS: return "invalid address";
	case INVALID_TAG: return "invalid tag";
	case DATA_TOO_LONG: return "data too long";
	case ERROR_READING_FLASH: return "error reading flash";
	case SE_ALREADY_INITIALIZED: return "secure element already initialized";
	case INVALID_FLASH_PAGE: return "invalid flash page";
	case INVALID_BASE64_DATA: return "invalid base64 data";
	case ERROR_WRITING_FLASH: return "error writing flash";
	case INVALID_JSON: return "invalid json data";
	case TOO_FEW_TX: return "too few transactions";
	case TOO_MANY_ADDRESSES: return "too many addresses";
	case INVALID_COUNT: return "invalid count";
	case INVALID_BIP_PATH: return "invalid BIP39 path";
	case NO_ACTIVE_SEED: return "no active seed";
	case INVALID_TRANSFERS: return "invalid transfers";
	case INVALID_INPUTS: return "invalid inputs";
	case INVALID_REMAINDER: return "invalid remainder";
	case INVALID_VALUE: return "invalid value";
	case INSUFFICIENT_TOKENS: return "insufficient tokens on inputs";
	case UNSPENT_TOKENS: return "unspent tokens";
//	case MISSING_PARAMETER: return "missing parameter";
//	case INVALID_PARAMETER: return "invalid parameter";
	case MISSING_OR_INVALID: return "missing or invalid parameter";
	case SEED_NOT_ACTIVATED: return "seed not activated";
	case INVALID_MESSAGE: return "invalid message";
	default: return "unknown error";
	}
}

const char* SBIResponse::errorMsg() {
	return errorMsg(code);
}

// switch from C to C++
// volatile needed because privileged functions are called via mret and
// GCC doesn't know this
extern "C" MMTEXT int riscv_sbi_call(uint32_t func) {
	return (int) SBI::sbiCall(func);
}

namespace SBI {
	namespace {
		#define MAX_NUM_SBI_CALLS  30

		typedef SBIError (*sbiCallType)();

		struct SbiCallsStruct {
			SBI_CALL call;
			sbiCallType fPtr;
		};

		SbiCallsStruct MMDATA sbiCalls[MAX_NUM_SBI_CALLS];
		uint32_t MMDATA numSBICalls = 0;


		SBIError MMTEXT _initSecureElement() {
			const char* key = initSecureElementRequest.key;
			if (!validateHex(key, 64)) {
				return SBIError::INVALID_KEY;
			}

			uint8_t keyBytes[32]={0};

			if (!hexToBytes(key, keyBytes, sizeof(keyBytes))) {
				return SBIError::INVALID_KEY;
			}

			if (Secure::isConfigLocked()) {
				return SBIError::SE_ALREADY_INITIALIZED;
			}

			if (!Secure::initSecureElement(keyBytes)) {
				return SBIError::INVALID_KEY;
			}

			if (!Secure::init()) {
				return SBIError::ERROR_START_SE;
			}
			return SBIError::NONE;
		}


		SBIError MMTEXT _setRandomSeed() {
			uint32_t slot = setRandomSeedRequest.slot;
			if (!Secure::isInitialized()) {
				return SBIError::SE_NOT_INITIALIZED;
			}

			if (/*slot < 0 ||*/ slot > 7) {
				return SBIError::INVALID_SLOT;
			}

			if (!seeds[slot]->writeRandomSeed()) {
				return SBIError::ERROR_SET_RANDOM_KEY;
			}
			return SBIError::NONE;
		}


		SBIError MMTEXT _startSecureElement() {
			if (!Secure::init()) {
				return SBIError::ERROR_START_SE;
			}
			return SBIError::NONE;
		}

		SBIError MMTEXT _doPoW() {
			const char* trytes = doPoWRequest.trytes;
			uint32_t mwm = doPoWRequest.mwm;
			powFPGA.doPoW(trytes, &doPoWResponse.nonce[0], mwm);
			return SBIError::NONE;
		}

		SBIError MMTEXT _curlHash() {
			const char* trytes = curlHashRequest.trytes;
			curlHashResponse.mwm = powFPGA.powCurlHash(trytes, &curlHashResponse.hash[0]);
			return SBIError::NONE;
		}


		SBIError MMTEXT _signBundleHash() {
			uint32_t slot = signBundleHashRequest.slot;
			uint32_t security = signBundleHashRequest.security;
			uint32_t keyIndex = signBundleHashRequest.keyIndex;
			const char* bundleHash = signBundleHashRequest.bundleHash;
			const uint8_t* auth = signBundleHashRequest.auth;
			if (!Secure::isInitialized()) {
				return SBIError::SE_NOT_INITIALIZED;
			}

			if (/*slot < 0 ||*/ slot > 7) {
				return SBIError::INVALID_SLOT;
			}

			if (security < 1 || security > 3) {
				return SBIError::INVALID_SECLEVEL;
			}

			if (!validateTrytes(bundleHash, BUNDLEHASH_LEN, false)) {
				return SBIError::INVALID_BUNDLE_HASH;
			}

			if (apiFlags.authMethod == API::AUTH) {
				uint8_t authBytes[48];
				pKeccak384->init();
				pKeccak384->update((uint8_t*) &slot, sizeof(uint32_t));
				pKeccak384->update((uint8_t*) &keyIndex, sizeof(uint32_t));
				pKeccak384->update((uint8_t*) bundleHash, BUNDLEHASH_LEN);
				pKeccak384->update((uint8_t*) Secure::getApiKey(), 48);
				pKeccak384->final(authBytes);

				if (memcmp(auth, authBytes, 48)) {
					return SBIError::INVALID_AUTH;
				}
			} else {
				// not yet implemented
				return SBIError::ERROR;
			}
			// convert bundleHashFragement to trytes
			tryte_t bundleHashTrytes[BUNDLEHASH_LEN] = { 0 };
			Conversion conv;
			conv.chars_to_trytes(bundleHash, bundleHashTrytes, BUNDLEHASH_LEN);

			if (!normalize_hash(bundleHashTrytes, security)) {
				return SBIError::INSECURE_BUNDLE_HASH;
			}

			SecureSeed* seed = seeds[slot];
			if (slot == BIP39_SLOT) {
				if (!seed->isValid()) {
					return SBIError::SEED_NOT_ACTIVATED;
				}
			} else {
				if (!seeds[slot]->load()) {
					return SBIError::ERROR_LOADING_KEY;
				}
			}

			Signing signing(pKeccak384, seed->getSeedBytes(), keyIndex);

			for (uint32_t i=0;i<security;i++) {
				txs[i].init();
				txs[i].getRawPtr()[2187]=0;	// zero terminate signature fragment
				signing.sign(&txs[i], &bundleHashTrytes[i*27]);
				signBundleHashResponse.signaturFragments[i] = &txs[i];
			}

			// release seed if not cached
			if (slot != BIP39_SLOT) {
				seed->release();
			}
			return SBIError::NONE;
		}


		SBIError MMTEXT _testHardwareAcceleration() {
#ifdef ENABLE_FPGA_TESTS
			testAccelerationResponse.pow = Tests::doPoW();
			testAccelerationResponse.curl = Tests::curl();
			testAccelerationResponse.keccak384 = Tests::keccak384();
			testAccelerationResponse.troika = Tests::troika();
			testAccelerationResponse.bigintToTritsRandomRepeated = Tests::bigintToTritsRandom();
			testAccelerationResponse.tritsToBigintRandomRepeated = Tests::tritsToBigintRandom();
			testAccelerationResponse.trytesToBigintRandomRepeated = Tests::trytesToBigintRandom();
			testAccelerationResponse.bigintToTrytesRandomRepeated = Tests::bigintToTrytesRandom();
			testAccelerationResponse.sha512 = Tests::sha512();
#endif
			return SBIError::NONE;
		}


		SBIError MMTEXT _readFlash() {
			uint32_t page = readFlashRequest.page;

			if (page >= 4096) {
				return SBIError::INVALID_FLASH_PAGE;
			}

			int Status = XST_FAILURE;
			uint8_t* p = (uint8_t*) &buffer.scratch[0];
			for (uint32_t n = page * SPIFlash::SECTORS_PER_PAGE; n < (page+1) * SPIFlash::SECTORS_PER_PAGE; n++) {
				Status = SPIFlash::readPage(&SPIFlash::Spi, n, (uint8_t*) p, SPIFlash::Command::COMMAND_QUAD_READ);
				if (Status != XST_SUCCESS) {
					return SBIError::ERROR_READING_FLASH;
				}
				p+=SPIFlash::SECTOR_LENGTH;
			}
			readFlashResponse.data = (uint8_t*) &buffer.scratch[0];
			return SBIError::NONE;
		}


		// "auth" checksums the data implicitely
		SBIError MMTEXT _writeFlash() {
			uint32_t page = writeFlashRequest.page;
			const uint8_t* auth = writeFlashRequest.auth;

			if (page >= 4096) {
				return SBIError::INVALID_FLASH_PAGE;
			}

			uint8_t authBytes[48];
			pKeccak384->init();
			pKeccak384->update((uint8_t*) &page, sizeof(uint32_t));
			pKeccak384->update((uint8_t*) &buffer.scratch[0], SPIFlash::FLASH_PAGE_LENGTH);
			pKeccak384->update((uint8_t*) Secure::getApiKey(), 48);
			pKeccak384->final(authBytes);

			if (memcmp(auth, authBytes, 48)) {
				return SBIError::INVALID_AUTH;
			}

			int Status = XST_FAILURE;

			// always use tx-buffer to not make writing secure information possible by using arbitrary pointers
			uint8_t* p = (uint8_t*) &buffer.scratch[0];

			// 64kB sectors
			if (!(page % 16)) {
				Status = SPIFlash::writeEnable(&SPIFlash::Spi);
				if (Status != XST_SUCCESS) {
					return SBIError::ERROR_WRITING_FLASH;
				}
				// erase sector
				Status = SPIFlash::sectorErase(&SPIFlash::Spi, page * SPIFlash::SECTORS_PER_PAGE);
				if (Status != XST_SUCCESS) {
					return SBIError::ERROR_WRITING_FLASH;
				}
			}
			for (uint32_t n = page * SPIFlash::SECTORS_PER_PAGE; n < (page+1) * SPIFlash::SECTORS_PER_PAGE; n++) {

				Status = SPIFlash::writeEnable(&SPIFlash::Spi);
				if (Status != XST_SUCCESS) {
					return SBIError::ERROR_WRITING_FLASH;
				}
				// write sector
				Status = SPIFlash::writePage(&SPIFlash::Spi, n, (uint8_t*) p, SPIFlash::Command::COMMAND_QUAD_WRITE);
				if (Status != XST_SUCCESS) {
					return SBIError::ERROR_WRITING_FLASH;
				}
				// read sector
				Status = SPIFlash::readPage(&SPIFlash::Spi, n, &buffer.scratch[SPIFlash::FLASH_PAGE_LENGTH], SPIFlash::Command::COMMAND_QUAD_READ);
				if (Status != XST_SUCCESS) {
					return SBIError::ERROR_WRITING_FLASH;
				}
				// compare written and read data
				if (memcmp(p, &buffer.scratch[SPIFlash::FLASH_PAGE_LENGTH], SPIFlash::SECTOR_LENGTH)) {
					return SBIError::ERROR_WRITING_FLASH;
				}
				p+=SPIFlash::SECTOR_LENGTH;
			}
			return SBIError::NONE;
		}
	}

	// TODO: how to restrict this sbi-call?
	SBIError MMTEXT _reboot() {
		uint32_t address = rebootRequest.address;
		if ((address & 0x0000ffff) || address > 0x1000000) {
			return SBIError::INVALID_ADDRESS;
		}
		// iprog is triggered one second after
		SecureGPIO::triggerIPROG(address);
		return SBIError::NONE;
	}

	SBIError MMTEXT _prepareTransfersLC() {
		uint32_t timestamp = prepareTransfersRequest.timestamp;
		uint32_t slot = prepareTransfersRequest.slot;
		uint32_t security = prepareTransfersRequest.security;
		uint32_t numTX = prepareTransfersRequest.numTX;
		const uint8_t* auth = prepareTransfersRequest.auth;

		if (slot > 7 && slot != (uint32_t) -1) {
			return SBIError::INVALID_SLOT;
		}
		// security may be zero -> read security setting from seed
		if (security > 3) {
			return SBIError::INVALID_SECLEVEL;
		}
		// more TX than allowed?
		if (numTX > MAX_NUM_TRANSACTIONS) {
			return SBIError::TOO_MANY_TX;
		}

		// remove chaining of TXs
		// only trust links created in privileged mode
		for (uint32_t i=0;i<numTX;i++) {
			txs[i].deleteLink();
		}
		// check if total is 0
		int64_t total = 0;
		for (uint32_t i=0;i<numTX;i++) {
			total += txs[i].getValue();
		}
		if (total < 0ll) {
			return SBIError::UNSPENT_TOKENS;
		} else if (total > 0ll) {
			return SBIError::INSUFFICIENT_TOKENS;
		}

		// find out if seed is needed
		bool needsSeed = false;
		for (uint32_t i=0;i<numTX;i++) {
			if ((txs[i].getType() == Transaction::Type::Input) ||
				(txs[i].getType() == Transaction::Type::Remainder)) {

				needsSeed = true;	// input or remainder -> seed is needed
			}
		}

		// no slot given but seed needed ... error
		if (slot == (uint32_t) -1 && needsSeed) {
			return SBIError::ERROR;
		}

		// check auth
		// non-value doesn't need auth
		if (needsSeed && apiFlags.authMethod == API::AUTH) {
			pKeccak384->init();
			pKeccak384->update((uint8_t*) &slot, sizeof(uint32_t));
			pKeccak384->update((uint8_t*) &timestamp, sizeof(uint32_t));

			for (uint32_t i=0;i<numTX;i++) {
				pKeccak384->update((uint8_t*) txs[i].getAddress(), 81);
				switch (txs[i].getType()) {
				case Transaction::Type::Output: {
					int64_t value = txs[i].getValue();
					pKeccak384->update((uint8_t*) &value, sizeof(int64_t));

// nasty hack ... tag and message were copied including zero terminator
// needed for verification - remove terminator after absorbing
// but take care about not overflowing the field
					uint32_t tagLen = strnlen(txs[i].getTag(), 27);
					pKeccak384->update((uint8_t*) txs[i].getTag(), tagLen);
					if (tagLen < 27) {
						txs[i].getTag()[tagLen] = '9';
					}

					uint32_t messageLen = strnlen(txs[i].getsignatureMessageFragment(), 2187);
					pKeccak384->update((uint8_t*) txs[i].getsignatureMessageFragment(), messageLen);
					if (messageLen < 2187) {
						txs[i].getsignatureMessageFragment()[messageLen] = '9';
					}

					break;
				}
				case Transaction::Type::Input: {
					uint32_t keyIndex = txs[i].getAddressIndex();
					int64_t value = -txs[i].getValue();
					pKeccak384->update((uint8_t*) &keyIndex, sizeof(uint32_t));
					pKeccak384->update((uint8_t*) &value, sizeof(int64_t));
					break;
				}
				case Transaction::Type::Remainder: {
					uint32_t keyIndex = txs[i].getAddressIndex();
					pKeccak384->update((uint8_t*) &keyIndex, sizeof(uint32_t));
					break;
				}
				default:
					return SBIError::ERROR;
				}
			}
			uint8_t authBytes[48];
			pKeccak384->update((uint8_t*) Secure::getApiKey(), 48);
			pKeccak384->final(authBytes);

			if (memcmp(auth, authBytes, 48)) {
				return SBIError::INVALID_AUTH;
			}
		}

		const uint8_t* seedBytes = 0;

		// if seed is needed, load it
		if (needsSeed) {
			SecureSeed* seed = seeds[slot];
			if (slot == BIP39_SLOT) {
				// bip39 has to be activated
				if (!seed->isValid()) {
					return SBIError::SEED_NOT_ACTIVATED;
				}
			} else {
				if (!seed->load()) {
					return SBIError::ERROR_LOADING_KEY;
				}
			}

			// if security == 0 read from seed; bip39 always read from seed
			if (!security || slot == BIP39_SLOT) {
				security = seed->getSecurity();
			}
			seedBytes = seed->getSeedBytes();
		}

		// validate addresses by generating them from the indices
		for (uint32_t i=0;i<numTX;i++) {
			if ((txs[i].getType() == Transaction::Type::Input) ||
				(txs[i].getType() == Transaction::Type::Remainder)) {

				uint32_t keyIndex = txs[i].getAddressIndex();

				char address[81]={0};
				Address::getAddressTrytes(seedBytes, keyIndex, security, address);
				if (memcmp(address, txs[i].getAddress(), 81)) {
					return SBIError::INVALID_ADDRESS;
				}
			}
		}

		// returns 0 if it's not possible to sign (too little space for transactions)
		Transaction* first = Transfers::prepareTransfers(&bundle, security, txs, numTX, MAX_NUM_TRANSACTIONS, true, timestamp);
		if (!first) {
			return SBIError::ERROR;
		}

		// only confirm if there is value transfer
		if (needsSeed && apiFlags.authMethod == API::HUMAN) {
			if (!UI::confirmTransactions(numTX)) {
				return SBIError::INVALID_AUTH;
			}
		}

		// only sign if there is value transfer
		if (needsSeed) {
			first = Transfers::signTransactions(&bundle, first, seedBytes, security);
			if (!first) {
				return SBIError::ERROR;
			}
		}

		// reverse TX list
		first = Transfers::reverseTransactions(first);
		if (!first) {
			return SBIError::ERROR;
		}


		prepareTransfersResponse.first = first;
		memcpy(prepareTransfersResponse.bundleHash, first->getBundle(), 81);
		return SBIError::NONE;
	}

	// functions not accessible via json-API
	SBIError MMTEXT _randomBytes() {
		if (!Secure::generateRandomSeedBytes(getRandomBytesResponse.bytes)) {
			return SBIError::ERROR;
		}
		return SBIError::NONE;
	}


	SBIError MMTEXT _setActiveSeed() {
		uint32_t* path = setActiveSeedRequest.path;
		uint32_t security = setActiveSeedRequest.security;

		for (int i=0;i<4;i++) {
			if (!(path[i] & 0x80000000)) {
				return SBIError::INVALID_BIP_PATH;
			}
		}
		if (security < 1 || security > 3) {
			return SBIError::INVALID_SECLEVEL;
		}
		if (!Secure::isInitialized()) {
			return SBIError::SE_NOT_INITIALIZED;
		}

		// we now it's a bip39 seed
		SecureSeedBIP39* seed = (SecureSeedBIP39*) seeds[BIP39_SLOT];
		seed->setCached(true);
		seed->setSecurity(security);
		if (!seed->loadBIP39(path)) {
			return SBIError::ERROR_LOADING_KEY;
		}
//			char seedTrytes[82]={0};	// incl zero terminator
//			ConversionFPGA conv;
//			conv.bytes_to_chars((const uint8_t*) seedBytes, seedTrytes, 48);
//			debugPrintf("%s\n", seedTrytes);
		return SBIError::NONE;
	}


	SBIError MMTEXT _getAddressLC() {
		uint32_t slot = getAddressRequest.slot;
		uint32_t security = getAddressRequest.security;
		uint32_t keyIndex = getAddressRequest.keyIndex;
		bool checksum = getAddressRequest.checksum;
		bool display = getAddressRequest.display;
		if (slot >= 8) {
			return SBIError::INVALID_SLOT;
		}

		SecureSeed* seed = seeds[slot];
		if (slot == BIP39_SLOT) {
			// bip39 seed needs to be activated
			if (!seed->isValid()) {
				return SBIError::SEED_NOT_ACTIVATED;
			}
		} else {
			// other seeds simply can be loaded
			if (!seed->load()) {
				return SBIError::ERROR_LOADING_KEY;
			}
		}

		if (security > 3) {
			return SBIError::INVALID_SECLEVEL;
		}
		if (!security || slot == BIP39_SLOT) {
			security = seed->getSecurity();
		}

		unsigned char bytes[48];
		Address::getPublicAddr(seed->getSeedBytes(), keyIndex, security, bytes);
		Address::getAddressWithChecksum(bytes, getAddressResponse.address);
		if (!checksum) {
			getAddressResponse.address[81] = 0;	// terminate string earlier if no checksum needed
		}

		// only bip39 seed always cached
		if (slot != BIP39_SLOT) {
			seed->release();
		}

		// do it here with privileges?
		if (display) {
			// display it on display TODO
		}
		return SBIError::NONE;
	}

	// mcycle only is accessible in privileged mode
	// it gives back ticks @ 100MHz since last boot
	SBIError MMTEXT _mcycle() {
		mCycleResponse.ticks = getMCycle();
		return SBIError::NONE;
	}

	bool MMTEXT registerSBICall(SBI_CALL call, sbiCallType fPtr) {
		if (numSBICalls >= MAX_NUM_SBI_CALLS) {
			return false;
		}
		sbiCalls[numSBICalls].call = call;
		sbiCalls[numSBICalls].fPtr = fPtr;
		numSBICalls++;
		return true;
	}

	bool MMTEXT registerSBICalls() {
		return
				registerSBICall(INIT_SECURE_ELEMENT, &_initSecureElement) &&
				registerSBICall(SET_RANDOM_SEED, &_setRandomSeed) &&
				registerSBICall(START_SECURE_ELEMENT, &_startSecureElement) &&
				registerSBICall(POW, &_doPoW) &&
				registerSBICall(SIGN_BUNDLEHASH, &_signBundleHash) &&
				registerSBICall(CURL, &_curlHash) &&
				registerSBICall(TEST_ACCELERATION, &_testHardwareAcceleration) &&
				registerSBICall(READ_FLASH, &_readFlash) &&
				registerSBICall(WRITE_FLASH, &_writeFlash) &&
				registerSBICall(REBOOT, &_reboot) &&
				registerSBICall(RANDOM_BYTES, &_randomBytes) &&
				registerSBICall(SET_ACTIVE_SEED_LC, &_setActiveSeed) &&
				registerSBICall(GET_ADDRESS_LC, &_getAddressLC) &&
				registerSBICall(PREPARE_TRANSFERS_LC, &_prepareTransfersLC) &&
				registerSBICall(MCYCLE, &_mcycle);
	}

	SBIError MMTEXT sbiCall(uint32_t func) {
		for (uint32_t i=0;i<numSBICalls;i++) {
			if (sbiCalls[i].call == func) {
				return sbiCalls[i].fPtr();
			}
		}
		return SBIError::UNKNOWN_CALL;
	}

	// user-code callable functions
	SBIResponse* initSecureElement(const char* key) {
		initSecureElementRequest.key = key;
		statusResponse.code = SBI_CALL_0(INIT_SECURE_ELEMENT);
		return &statusResponse;
	}

	SBIResponse* startSecureElement() {
		statusResponse.code = SBI_CALL_0(START_SECURE_ELEMENT);
		return &statusResponse;
	}

	SBIResponse* setRandomSeed(int slot) {
		setRandomSeedRequest.slot = slot;
		statusResponse.code = SBI_CALL_0(SET_RANDOM_SEED);
		return &statusResponse;
	}

	DoPoWResponse* doPoW(const char* trytes, uint32_t mwm) {
		doPoWRequest.trytes = trytes;
		doPoWRequest.mwm = mwm;
		doPoWResponse.code = SBI_CALL_0(POW);
		return &doPoWResponse;
	}

	CurlHashResponse* curlHash(const char* trytes) {
		curlHashRequest.trytes = trytes;
		curlHashResponse.code = SBI_CALL_0(CURL);
		return &curlHashResponse;
	}

	SignBundleHashResponse* signBundleHash(uint32_t slot, uint32_t security, uint32_t keyIndex, const char* bundleHash, const uint8_t* auth) {
		signBundleHashRequest.slot = slot;
		signBundleHashRequest.security = security;
		signBundleHashRequest.keyIndex = keyIndex;
		signBundleHashRequest.bundleHash = bundleHash;
		signBundleHashRequest.auth = auth;
		signBundleHashResponse.code = SBI_CALL_0(SIGN_BUNDLEHASH);
		return &signBundleHashResponse;
	}

	TestAccelerationResponse* testHardwareAcceleration() {
		testAccelerationResponse.code = SBI_CALL_0(TEST_ACCELERATION);
		return &testAccelerationResponse;
	}

	ReadFlashResponse* readFlash(uint32_t page) {
		readFlashRequest.page = page;
		readFlashResponse.code = SBI_CALL_0(READ_FLASH);
		return &readFlashResponse;
	}

	SBIResponse* writeFlash(uint32_t page, const uint8_t* auth) {
		writeFlashRequest.page = page;
		writeFlashRequest.auth = auth;
		statusResponse.code = SBI_CALL_0(WRITE_FLASH);
		return &statusResponse;
	}

	SBIResponse* reboot(uint32_t address) {
		rebootRequest.address = address;
		statusResponse.code = SBI_CALL_0(REBOOT);
		return &statusResponse;
	}

	GetRandomBytesResponse* randomBytes() {
		getRandomBytesResponse.code = SBI_CALL_0(RANDOM_BYTES);
		return &getRandomBytesResponse;
	}

	SBIResponse* setActiveSeedLC(uint32_t path[4], uint32_t security) {
		memcpy(setActiveSeedRequest.path, path, 4*sizeof(uint32_t));
		setActiveSeedRequest.security = security;
		statusResponse.code = SBI_CALL_0(SET_ACTIVE_SEED_LC);
		return &statusResponse;
	}


	GetAddressResponse* getAddressLC(uint32_t slot, uint32_t security, uint32_t index, bool checksum, bool display) {
		getAddressRequest.slot = slot;
		getAddressRequest.security = security;
		getAddressRequest.keyIndex = index;
		getAddressRequest.checksum = checksum;
		getAddressRequest.display = display;
		getAddressResponse.code = SBI_CALL_0(GET_ADDRESS_LC);
		return &getAddressResponse;
	}

	PrepareTransfersResponse* prepareTransfersLC(uint32_t timestamp, uint32_t slot, uint32_t security, uint32_t numTX, const uint8_t* auth) {
		prepareTransfersRequest.timestamp = timestamp;
		prepareTransfersRequest.slot = slot;
		prepareTransfersRequest.security = security;
		prepareTransfersRequest.numTX = numTX;
		prepareTransfersRequest.auth = auth;
		prepareTransfersResponse.code = SBI_CALL_0(PREPARE_TRANSFERS_LC);
		return &prepareTransfersResponse;
	}

	MCycleResponse* getMCycles() {
		mCycleResponse.code = SBI_CALL_0(MCYCLE);
		return &mCycleResponse;
	}

	void switchUserMode() {
		SBI_CALL_0(SWITCH_USERMODE);
	}

	bool init() {
		powFPGA.init();

		for (int i=0;i<MAX_NUM_TRANSACTIONS;i++) {
			txs[i].setDataPointer(&buffer.txsBuffer[i]);	// init all
			txs[i].init();
		}

		if (!registerSBICalls()) {
			return false;
		}
		return true;
	}

}


